﻿// =====================================================
// Copyright (C) 2016-2019 Fanjia
// All rights reserved
// 机器名称: PC-LIWEI
// 创建时间: 2019/3/19 10:13:46
// 创 建 人: 李伟
// ======================================================

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using Nest;

namespace WindowsFormsApplication1.Common
{
    /// <summary>
    /// 字符串拓展类-用于文本相似度比较
    /// </summary>
    public static class SimilarityStringExtension
    {
        /// <summary>
        /// returns a string buffer of characters from string1 within string2 if they are of a given
        /// distance separation from the position in string1.
        /// </summary>
        /// <param name="firstWord">string one</param>
        /// <param name="secondWord">string two</param>
        /// <param name="distanceSep">separation distance</param>
        /// <returns>a string buffer of characters from string1 within string2 if they are of a given
        /// distance separation from the position in string1</returns>
        private static StringBuilder GetCommonCharacters(string firstWord, string secondWord, int distanceSep)
        {
            var returnCommons = new StringBuilder();
            var copy = new StringBuilder(secondWord);
            for (var i = 0; i < firstWord.Length; i++)
            {
                var ch = firstWord[i];
                var foundIt = false;
                for (var j = Math.Max(0, i - distanceSep);
                    !foundIt && j < Math.Min(i + distanceSep, secondWord.Length);
                    j++)
                {
                    if (copy[j] != ch) continue;
                    foundIt = true;
                    returnCommons.Append(ch);
                    copy[j] = '#';
                }
            }
            return returnCommons;
        }

        /// <summary>
        /// gets the similarity of the two strings using Jaro distance.
        /// </summary>
        /// <param name="firstWord"></param>
        /// <param name="secondWord"></param>
        /// <returns>a value between 0-1 of the similarity</returns>
        private static double GetJaroSimilarity(string firstWord, string secondWord)
        {
            //get half the length of the string rounded up - (this is the distance used for acceptable transpositions)
            var halfpence = Math.Max(firstWord.Length, secondWord.Length) / 2 + 1;
            //get common characters
            var common1 = GetCommonCharacters(firstWord, secondWord, halfpence);
            var commonMatches = common1.Length;
            //check for zero in common
            if (commonMatches == 0)
            {
                return 0;
            }
            var common2 = GetCommonCharacters(secondWord, firstWord, halfpence);
            //check for same length common strings returning 0.0f is not the same
            if (commonMatches != common2.Length)
            {
                return 0;
            }
            //get the number of transpositions
            var transpositions = 0;
            for (var i = 0; i < commonMatches; i++)
            {
                if (common1[i] != common2[i])
                {
                    transpositions++;
                }
            }

            //calculate jaro metric
            transpositions /= 2;
            var jaroSimilarity = commonMatches / (3.0 * firstWord.Length)
                       + commonMatches / (3.0 * secondWord.Length)
                       + (commonMatches - transpositions) / (3.0 * commonMatches);
            return jaroSimilarity;
        }

        /// <summary>
        /// gets the prefix length found of common characters at the begining of the strings.
        /// </summary>
        /// <param name="firstWord"></param>
        /// <param name="secondWord"></param>
        /// <returns>the prefix length found of common characters at the begining of the strings</returns>
        private static int GetPrefixLength(string firstWord, string secondWord)
        {
            var n = Math.Min(Math.Min(firstWord.Length, secondWord.Length), 4);
            for (var i = 0; i < n; i++)
            {
                if (firstWord[i] != secondWord[i])
                {
                    return i;
                }
            }
            return n;
        }

        /// <summary>
        /// 获取 JaroWinkler 相似度
        /// </summary>
        /// <param name="source">源字符串</param>
        /// <param name="target">目标字符串</param>
        /// <returns></returns>
        public static double GetJaroWinklerSimilarity(this string source, string target)
        {
            if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(target))
            {
                return 0;
            }

            var s = source.ToCleanStr();
            var t = target.ToCleanStr();
            if (s == t)
            {
                return 1;
            }

            var jaro = GetJaroSimilarity(s, t);
            var prefixLength = GetPrefixLength(s, t);
            return jaro + prefixLength * 0.1 * (1 - jaro);
        }

        /// <summary>
        /// 清理字符串
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public static string ToCleanStr(this string input)
        {
            return string.IsNullOrEmpty(input) ?
                string.Empty :
                Regex.Replace(input, @"[\s|\·|\—|\-|\(|\)|\【|\】|\（|\）]", "", RegexOptions.IgnoreCase);
        }

        /// <summary>
        /// 从XML字符串中反序列化对象
        /// </summary>
        /// <typeparam name="T">结果对象类型</typeparam>
        /// <param name="input">包含对象的XML字符串</param>
        /// <param name="encoding">编码方式</param>
        /// <returns>反序列化得到的对象</returns>
        public static T XmlDeserialize<T>(this string input, Encoding encoding = null)
        {
            if (encoding == null)
            {
                encoding = Encoding.UTF8;
            }
            if (string.IsNullOrEmpty(input))
                throw new ArgumentNullException(input);
            var mySerializer = new XmlSerializer(typeof(T));
            using (var ms = new MemoryStream(encoding.GetBytes(input)))
            {
                using (var sr = new StreamReader(ms, encoding))
                {
                    return (T)mySerializer.Deserialize(sr);
                }
            }
        }
    }

    public static class GeoCoordinateExtension
    {
        private const double Xpi = Math.PI * 3000 / 180;

        /// <summary>
        /// 将百度坐标系（BD-09）转换成火星坐标系（GCJ-02）
        /// </summary>
        /// <param name="geoCoordinate"></param>
        /// <returns>火星坐标系的经纬度</returns>
        public static GeoCoordinate TransformBd09ToGcj02(this GeoCoordinate geoCoordinate)
        {
            var longitude = geoCoordinate.Longitude;
            var latitude = geoCoordinate.Latitude;
            if (longitude < 72.004 || longitude > 137.8347 || latitude < 0.8293 || latitude > 55.8271)
            {
                return default(GeoCoordinate);
            }
            var x = longitude - 0.0065;
            var y = latitude - 0.006;
            var z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * Xpi);
            var theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * Xpi);
            return new GeoCoordinate(z * Math.Sin(theta), z * Math.Cos(theta));
        }

        /// <summary>
        /// 获取地理距离
        /// </summary>
        /// <param name="source">来源</param>
        /// <param name="target">目标</param>
        /// <returns>地理距离</returns>
        public static double GetGeoDistance(this GeoCoordinate source, GeoCoordinate target)
        {
            if (source == default(GeoCoordinate) || target == default(GeoCoordinate))
            {
                return default(double);
            }

            if (Math.Abs(source.Longitude) > 180 || Math.Abs(source.Latitude) > 90 ||
                Math.Abs(target.Longitude) > 180 || Math.Abs(target.Latitude) > 90)
            {
                return default(double);
            }

            return source.GetDistanceTo(target);
        }

        /// <summary>
        /// 获取坐标相似度
        /// </summary>
        /// <param name="source">来源</param>
        /// <param name="target">目标</param>
        /// <returns>坐标相似度</returns>
        public static double GetCoordinateSimilarity(this GeoCoordinate source, GeoCoordinate target)
        {
            try
            {
                var geoDistance = GetGeoDistance(source, target);
                return geoDistance <= 100 ? 1 : (geoDistance > 1000 ? 0 : Math.Floor((1100 - geoDistance) / 100) / 10);
            }
            catch (Exception e)
            {
                return 0;
            }
        }
    }

    public class SimilarityGenerator
    {
        public static double GetCoordinateSimilarity(
            decimal? supplierLatitude,
            decimal? supplierLongitude,
            decimal? fansCarLatitude,
            decimal? fansCarLongitude
        )
        {
            var coordinate = new GeoCoordinate(
                (double)(supplierLatitude ?? 0M),
                (double)(supplierLongitude ?? 0M));

            var fansCarCoordinate = new GeoCoordinate(
                (double)(fansCarLatitude ?? 0M),
                (double)(fansCarLongitude ?? 0M));


            var hotelCoordinateSimilarity = coordinate.GetCoordinateSimilarity(fansCarCoordinate);

            return hotelCoordinateSimilarity;
        }

        public static double GetNameSimilarity(string supplierHotelName, string hotelName)
        {
            // 酒店名称相似度
            var hotelNameSimilarity = supplierHotelName.GetJaroWinklerSimilarity(hotelName);
            return hotelNameSimilarity;
        }

        public static decimal GetPhoneSimilarity(string supplierHotelPhone, string hotelPhone)
        {
            if (string.IsNullOrEmpty(supplierHotelPhone) || string.IsNullOrEmpty(hotelPhone))
            {
                return 0;
            }

            var hotelPhoneSimilarity = string.IsNullOrWhiteSpace(supplierHotelPhone) ||
                                       supplierHotelPhone.StartsWith(@"\n") ||
                                       supplierHotelPhone.StartsWith("400") ||
                                       supplierHotelPhone.ToCleanStr() != hotelPhone.ToCleanStr() ? 0 : 1;

            return hotelPhoneSimilarity;
        }
    }
}
